#ifdef ENGINE_GLFW

#include "GLEngine.h"
#include "GLClient.h"
#include "../gl/mygl_ext.h"

static GLEngine* g_engine = 0;

void GLFWCALL engine_callback_char(int c, int action)
{
    GLEvent e;
	if (action == GLFW_PRESS)
	{
	    e.type = EVT_KeyChar;
	    e.key.name = c;
		g_engine->sendEvent(e);
	}
}

void GLFWCALL engine_callback_key(int key, int action)
{
    GLEvent e;
	if (action == GLFW_PRESS)
	{
	    e.type = EVT_KeyPress;
	    e.key.code = key;
	    g_engine->sendEvent(e);
	}
	else if (action == GLFW_RELEASE)
	{
		e.type = EVT_KeyRelease;
		e.key.code = key;
		g_engine->sendEvent(e);
	}
}

void GLFWCALL engine_callback_mousePos(int x, int y)
{
    GLEvent e;
    e.type = EVT_MouseMove;
    e.mouse.x = x;
    e.mouse.y = y;
    g_engine->sendEvent(e);
}


void GLFWCALL engine_callback_mouseButton(int button, int action)
{
    GLEvent e;
	if (action == GLFW_PRESS)
	{
		e.type = EVT_MousePress;
		e.mouse.button = button;
		g_engine->sendEvent(e);
	}
	else if (action == GLFW_RELEASE)
	{
		e.type = EVT_MouseRelease;
		e.mouse.button = button;
		g_engine->sendEvent(e);
	}
}

void GLFWCALL engine_callback_mouseWheel(int pos)
{
    static int last_pos = 0;
	GLEvent e;
	e.type = EVT_MouseWheel;
	e.mouse.wheel = last_pos - pos;
	last_pos = pos;
	g_engine->sendEvent(e);
}

void GLFWCALL engine_callback_windowSize(int width, int height)
{
	GLEvent e;
	e.type = EVT_Resize;
	e.resize.w = width;
	e.resize.h = height;
	g_engine->sendEvent(e);
	//viewport
	g_engine->setSize(width, height);
}


GLEngine::GLEngine(const char *title, int width, int height, int bpp, bool fullscreen,
                   int vsync, int multisamples)
{
    printf("Init engine...\n");
    printf("  screen: %i %i %i\n", width, height, bpp);
    printf("  fullscreen: %i\n", fullscreen);
    printf("  vsync: %i\n", vsync);
    printf("  multisamples: %i\n", multisamples);

    glfwInit();

    //32-bit default
    int rr = 8;
    int gg = 8;
    int bb = 8;
    int aa = 8;
    //16-bit
    if (bpp == 16)
    {
        rr = 5;
        gg = 6;
        bb = 5;
        aa = 0;
    }
    //24-bit
    else if (bpp == 24)
    {
        aa = 0;
    }

    glfwOpenWindowHint(GLFW_FSAA_SAMPLES, multisamples);
	if (!glfwOpenWindow(width, height,
                        rr, gg, bb, aa, 8, 0,
                        fullscreen ? GLFW_FULLSCREEN : GLFW_WINDOW))
    {
    	printf("FAILED INITIALIZING GLFW!\n");
    }

	glfwSetWindowTitle(title);
	glfwSwapInterval(vsync);
    glfwDisable(GLFW_AUTO_POLL_EVENTS);
    glfwEnable(GLFW_KEY_REPEAT);
    if (fullscreen)
    {
		glfwEnable(GLFW_MOUSE_CURSOR);
    }
    else
    {
        //center screen
        GLFWvidmode vm;
        glfwGetDesktopMode(&vm);
        glfwSetWindowPos(vm.Width/2 - width/2, vm.Height/2 - height/2);
    }

    g_engine = this;

	winWidth = width;
	winHeight = height;
	winFullscreen = fullscreen;
	winVSync = vsync;

	client = NULL;
	next_client = NULL;
	max_fps = 0;
    quit_code = 0;
	audio = NULL;

	console_visible = false;
	console_font = NULL;
	console_max_lines = 10;

    trans_mode = TRA_None;
    trans_timeout = 0;
    trans_start_time = 0;
    trans_id = 0;

	glDefaultSetup();

	glfwSetKeyCallback(engine_callback_key);
	glfwSetCharCallback(engine_callback_char);
	glfwSetMouseButtonCallback(engine_callback_mouseButton);
	glfwSetMousePosCallback(engine_callback_mousePos);
	glfwSetMouseWheelCallback(engine_callback_mouseWheel);
	glfwSetWindowSizeCallback(engine_callback_windowSize);
}


GLEngine::~GLEngine()
{
    delete audio;
    foreach (GLClient*, cli, m_Clients)
    {
        delete cli;
    }
	glfwTerminate();
}


bool GLEngine::doEvents()
{
    //input update
    glfwPollEvents();

    //audio update
    if (audio)
        audio->update();

    //close window
    if (!glfwGetWindowParam(GLFW_OPENED))
    {
    	return false;
    }
    //CONTINUE RUNNING
    return true;
}


void GLEngine::beginDraw()
{
    //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glViewport(0, 0, winWidth, winHeight);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void GLEngine::endDraw()
{
    //console
    if (console_visible && console_font)
    {
        float hh = console_font->getFontInfo()->common.lineHeight;
        glBeginSceneGUI(winWidth, winHeight);
        glFillRect(rectf(0, 0, winWidth, (console_max_lines+1)*hh), Color(0x80000000));
        int n = mMin(console_max_lines, console_lines.size());
        int m = console_lines.size() - 1;
        float yy = 0;
        for (int i = n-1; i >= 0; i--)
        {
            glDrawText(console_font, 0, yy, console_lines[m-i].cmd, console_lines[m-i].col);
            yy += hh;
        }
        String s("> ");
        s += console_cur_line;
        s += '_';
        glDrawText(console_font, 0, yy, s, Color::White);
        glEndSceneGUI();
    }
    //transition
    if (trans_mode != TRA_None)
    {
        float f = trans_timeout / trans_start_time;
        if (trans_mode == TRA_FadeOut)
            f = 1 - f;
        Color c(f * 255, 0, 0, 0);
        glBeginSceneGUI(winWidth, winHeight);
        glFillRect(rectf(0, 0, winWidth, winHeight), c);
        glEndSceneGUI();
    }
    //present OpenGL scene
    glfwSwapBuffers();
}


void GLEngine::sendEvent(GLEvent& e)
{
    if (trans_mode != TRA_None)
        return;
    //process console event
    if (console_font)
    {
        if (console_visible)
        {
            if (e.type == EVT_KeyPress)
            {
                if (e.key.code == KEY_F12 || e.key.code == KEY_ESC)
                {
                    console_visible = false;
                }
                else if (e.key.code == KEY_ENTER)
                {
                    pushConsoleLine(console_cur_line);
                    //console event
                    if (client)
                    {
                        GLEvent evt;
                        evt.type = EVT_Console;
                        evt.console.cmd = console_cur_line.c_str();
                        client->receiveEvent(evt);
                    }
                    console_cur_line = "";
                }
                else if (e.key.code == KEY_BACKSPACE)
                {
                    console_cur_line.erase(1);
                }
                return;
            }
            else if (e.type == EVT_KeyChar)
            {
                console_cur_line += e.key.name;
                return;
            }
            else if (e.type == EVT_KeyRelease)
            {
                return;
            }
        }
        else
        {
            if (e.type == EVT_KeyPress && e.key.code == KEY_F12)
            {
                console_visible = true;
            }
        }
    }
    //send event to client
    if (client)
        client->receiveEvent(e);
}


void GLEngine::quit(int ret)
{
    quit_code = ret;
    glfwCloseWindow();
}


//
// Clients
//

GLClient* GLEngine::getClient(const char* name)
{
    foreach (GLClient*, cli, m_Clients)
    {
        if (cli->getName() == name)
            return cli;
    }
    printf("WARNING: Client '%s' not found!\n", name);
    return NULL;
}

int GLEngine::runClient(const char* name)
{
	float t_frame = getTime();
	float t_delta = getTime();

	next_client = getClient(name);
    if (!next_client)
        return -1;

    while (doEvents())
    {
        if (next_client)
        {
            if (client) client->onLeave();
            client = next_client;
            next_client = NULL;
            client->refresh();
            client->onEnter();
        }
        if (isActive())
        {
            if (client)
            {
                t_frame = getTime();
                float dt = getTime() - t_delta;
                t_delta = getTime();
                //update client
                client->update(dt);
                //draw client
                beginDraw();
                client->draw();
                endDraw();
                //update transition
                if (trans_mode != TRA_None)
                {
                    trans_timeout -= dt;
                    if (trans_timeout <= 0)
                        stopTransition();
                }
                //sleep
                if (max_fps > 0.0f)
                {
                    float rt = 1.0f / max_fps - (getTime() - t_frame);
                    glfwSleep(rt);
                }
            }
        }
        else
        {
            wait();
        }
    }

	if (client) client->onLeave();

	return quit_code;
}

void GLEngine::changeClient(const char* name)
{
    next_client = getClient(name);
}

//
//resources
//

GLTexture* GLEngine::getTexture(const char* file_name, int flags)
{
	int i = m_TextureManager.find(file_name);
	if (i == -1)
    {
		return m_TextureManager.create(file_name, GLTexture::createFromFile(file_name, flags));
    }
	else
    {
        m_TextureManager.increase(i);
        return m_TextureManager.get(i);
    }
}

GLModel* GLEngine::getModel(const char* file_name, float scale, int flags)
{
	int i = m_ModelManager.find(file_name);
	if (i == -1)
	{
		return m_ModelManager.create(file_name, GLModel::createFromFile(file_name, scale, flags));
	}
	else
    {
        m_ModelManager.increase(i);
        return m_ModelManager.get(i);
    }
}

GLFont* GLEngine::getFont(const char* file_name)
{
    int i = m_FontManager.find(file_name);
    if (i == -1)
    {
        return m_FontManager.create(file_name, GLFont::createFromFile(file_name));
    }
    else
    {
        m_FontManager.increase(i);
        return m_FontManager.get(i);
    }
}

// release

void GLEngine::releaseTexture(GLTexture* tex)
{
}

void GLEngine::releaseModel(GLModel* mdl)
{
}

void GLEngine::releaseFont(GLFont* fnt)
{
}

// console

void GLEngine::initConsole(GLFont* font, int max_lines)
{
    console_font = font;
    if (console_font)
    {
        console_max_lines = max_lines;
        console_lines.clear();
    }
}

void GLEngine::pushConsoleLine(const String& s, Color c)
{
    ConLine cl;
    cl.cmd = s;
    cl.col = c;
    console_lines.push_back(cl);
}

// transition

void GLEngine::startTransition(GLEngineTransition trans, float timeout, int id)
{
    trans_mode = trans;
    trans_timeout = timeout;
    trans_start_time = timeout;
    trans_id = id;
}

void GLEngine::stopTransition()
{
    if (client)
    {
        GLEvent e;
        e.type = EVT_TransitionEnd;
        e.transition.id = trans_id;
        e.transition.mode = trans_mode;
        client->receiveEvent(e);
    }
    trans_mode = TRA_None;
    trans_timeout = 0;
    trans_id = 0;
}

#endif // ENGINE_GLFW

